Skip to content

Performance audit sweep: cloning, async I/O, indexing, parallel discovery#12

Merged
tonythethompson merged 6 commits into
masterfrom
codex/performance-audit
Jul 3, 2026
Merged

Performance audit sweep: cloning, async I/O, indexing, parallel discovery#12
tonythethompson merged 6 commits into
masterfrom
codex/performance-audit

Conversation

@tonythethompson

@tonythethompson tonythethompson commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the performance audit findings in docs/performance-audit.md, then fixes correctness/concurrency issues found via review and a follow-up targeted sweep.

Performance sweep:

  • Add Dictionary-backed name/ID indexes to ShortcutRepository, kept in sync across load/mutate/undo/redo
  • Add async preload/reload paths so shortcut file I/O doesn't block first query
  • Parallelize GitRepoDiscovery with a bounded worker pool, preserving result ordering and scan limits
  • Cut LINQ/allocation churn in Search()/SearchForRootPalette() via span-based query matching
  • Reduce undo/redo history retention from 50 → 25 snapshots
  • Dedupe WtProfilesService JSON parsing into a single ReadProfilesFromJson path
  • Defer default-profile choice enumeration at settings-manager construction, with opt-in startup timing via QUICKSHELL_STARTUP_TRACE=1

Bugs found and fixed during review:

  • HIGH: the deferred default-profile choice enumeration validated against the minimal ctor-seeded choice list instead of the live terminal catalog, silently resetting a user's custom default terminal profile on every shortcut launch until they opened the settings form. Now validates against the live catalog.
  • HIGH: TerminalCatalog's cached fields were read outside the lock after EnsureCached() returned, so a concurrent InvalidateCache() could null them mid-read and throw NullReferenceException. Bundled the cache into one atomically-swapped snapshot object.
  • HIGH: ShortcutForm subscribed to the static, process-lifetime Drafts.Cleared event with no reliable unsubscribe path if the form was abandoned (Escape, navigating away) instead of explicitly saved/canceled, permanently rooting the instance. Subscribes via a weak-reference trampoline now.
  • HIGH (bottleneck): GitRepoIndex.EnsureFresh held its lock for the full duration of the multi-second, multi-thousand-directory filesystem scan, serializing every other cache reader/invalidator behind it. The scan now runs off-lock via a coalesced single-flight task.
  • MEDIUM: hardened GitRepoDiscovery's pending-work counter so the stop-check and increment can't interleave, closing a theoretical stranded-item race in the parallel worker pool. Also switched child-directory enumeration to a streaming iterator so ShouldStop()/MaxDirectoriesScanned can short-circuit a very wide directory instead of materializing it via ToArray() first.
  • MEDIUM: ShortcutRepository.Dispose() disposed the persist timer without waiting for an in-flight callback, then immediately disposed the semaphore/mutex the callback uses — a shutdown race that could throw ObjectDisposedException on a background thread. Switched to Timer.Dispose(WaitHandle) to block until any in-flight callback finishes first.
  • MEDIUM: SettingsFormHelpers.ScheduleRefresh's fire-and-forget task had no exception handling, so a failure became an unobserved task exception. Now catches only the expected teardown exceptions (ObjectDisposedException, COMException); anything else still surfaces.
  • LOW: ShortcutFormTemplateCache now builds its template outside the lock so one slow build doesn't block unrelated callers.
  • LOW: removed dead ScheduleDebouncedReload code whose shared process-static CancellationTokenSource would have incorrectly canceled unrelated callers' pending reloads if it were ever wired up for more than one debounce source.

Security:

  • Removed sandbox_mode = "danger-full-access" / approval_policy = "never" from the committed .codex/config.toml — these granted full filesystem/network access with no approval gate to anyone cloning the repo or running Codex in CI. Left a comment pointing at setting them locally (untracked) instead.

Clarity:

  • Reworded the recent-workspaces setting's label/description to state plainly it's an on/off toggle with a fixed cap, not a literal count (the underlying NormalizeCount treats any non-zero value as "on").

Test plan

  • dotnet build QuickShell.sln
  • dotnet test QuickShell.Core.Tests\QuickShell.Core.Tests.csproj — 179 passed
  • Manual smoke test of shortcut launch with a custom default terminal profile set

🤖 Generated with Claude Code

Add /.bob directory and docs/performance-audit.md to .gitignore.
Copilot AI review requested due to automatic review settings July 3, 2026 16:08

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3340d522d6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread QuickShell.Core/Services/GitRepoDiscovery.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a performance-audit sweep across core services (shortcut repository, git repo discovery, startup warm-up/tracing) while adding targeted tests and documentation to lock in behavior and guide accessibility verification.

Changes:

  • Introduces async shortcut warm-up + opt-in startup tracing, plus lazy initialization for CmdPal fallback UI.
  • Optimizes core paths (shortcut lookups/search, git repo discovery parallelism, JSON parsing reuse) with new/updated unit tests.
  • Adds documentation for performance findings and accessibility testing; adjusts minor UI/form spacing and glyph mapping.

Reviewed changes

Copilot reviewed 27 out of 28 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
QuickShell/Services/TerminalCatalogChoices.cs Adds minimal default-profile choices helper to reduce eager enumeration.
QuickShell/Services/SettingsCardJson.cs Tweaks Adaptive Card spacing + replaces recent-count stepper with a toggle template.
QuickShell/Services/QuickShellRuntimeServices.cs Kicks off background shortcut preload during initialization.
QuickShell/QuickShellSettingsManager.cs Defers default-profile choices; updates “recents” setting strings and normalization flow.
QuickShell/QuickShellFallback.cs Lazily constructs fallback page and avoids repeated shortcut enumeration.
QuickShell/QuickShellCommandsProvider.cs Adds startup tracing spans + lazy fallback page construction and lifecycle handling.
QuickShell/Pages/TerminalDefaultsSettingsForm.cs Spacing tweaks on adaptive card inputs/actions.
QuickShell/Pages/ShortcutTransferSettingsForm.cs Spacing tweaks in transfer/import conflict UI.
QuickShell/Pages/HomeDisplaySettingsForm.cs Switches recents configuration from stepper to toggle + saves on change action.
QuickShell.Run/Main.cs Adds startup tracing spans + background shortcut preload kickoff.
QuickShell.Core/Services/WtProfilesService.cs Extracts single JSON parse path and exposes ReadProfilesFromJson for tests.
QuickShell.Core/Services/StartupPerformanceTrace.cs Adds opt-in startup timing via QUICKSHELL_STARTUP_TRACE.
QuickShell.Core/Services/ShortcutRepository.cs Adds indexes, async preload/reload, lower history cap, and reduces search allocations.
QuickShell.Core/Services/ShortcutRecents.cs Uses bounded display clamp for recents count.
QuickShell.Core/Services/ShortcutLayoutJson.cs Adds async JSON parse helper for async repository load.
QuickShell.Core/Services/ShortcutGlyphs.cs Swaps glyph mappings for Duplicate vs CopyPath.
QuickShell.Core/Services/QuickShellRecentSettings.cs Changes recents setting to enable/disable semantics with fixed cap.
QuickShell.Core/Services/GitRepoDiscovery.cs Adds bounded parallel discovery while preserving limits and stable ordering.
QuickShell.Core.Tests/WorkspaceUtilityTests.cs Adds tests for bounded parallel discovery + updated recent-count semantics.
QuickShell.Core.Tests/TerminalProfileIntegrationTests.cs Adds test for nested defaultProfile application in WT settings JSON.
QuickShell.Core.Tests/TerminalCatalogChoicesTests.cs Adds test for minimal default profile choices.
QuickShell.Core.Tests/StartupPerformanceTraceTests.cs Adds tests for env-var opt-in logic.
QuickShell.Core.Tests/ShortcutRepositoryPerformanceShapeTests.cs Adds performance-shape/correctness tests for indexing, allocations, async load.
QuickShell.Core.Tests/ShortcutDisplayTests.cs Updates glyph assertions and adds Duplicate glyph test.
docs/performance-audit.md Adds detailed performance audit report and implementation status.
ACCESSIBILITY.md Adds accessibility testing checklist and store-submission guidance.
.gitignore Stops ignoring ACCESSIBILITY.md; adds /.bob.
.codex/config.toml Sets Codex sandbox/approval defaults (security-sensitive).

Comment thread .codex/config.toml Outdated
Comment thread QuickShell/QuickShellSettingsManager.cs
- TerminalCatalog: cache fields were read outside the lock after
  EnsureCached() returned, so a concurrent InvalidateCache() could
  null them mid-read and throw NRE. Bundled the cache into one
  atomically-swapped snapshot object instead.
- ShortcutFormPage: ShortcutForm subscribed to the static, process-
  lifetime Drafts.Cleared event with no reliable unsubscribe path if
  the form was abandoned (Escape, navigate away) instead of explicitly
  saved/canceled, permanently rooting the instance. Subscribe via a
  weak-reference trampoline so an abandoned form can be collected.
- GitRepoIndex: EnsureFresh held the lock for the full duration of the
  (multi-second, multi-thousand-directory) filesystem scan, serializing
  every other cache reader/invalidator behind it. Scan now runs off
  the lock via a coalesced single-flight task.
- ShortcutRepository: Dispose() called Timer.Dispose() (no wait) then
  immediately disposed the semaphore/mutex the timer callback uses,
  leaving a shutdown race where an in-flight callback could hit
  ObjectDisposedException. Switched to Timer.Dispose(WaitHandle) to
  block until any in-flight callback finishes first.
- SettingsFormHelpers.ScheduleRefresh: fire-and-forget Task.Run had no
  try/catch, so an exception (e.g. page torn down mid-flight) became
  an unobserved task exception. Wrapped in a best-effort catch.

Found via a targeted concurrency/leak/bottleneck sweep across
QuickShell.Core, QuickShell, and QuickShell.Run; QuickShell.Run itself
had no new findings beyond the audit's already-documented per-keystroke
stat-file bottleneck.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

- ShortcutFormTemplateCache: build the template outside the lock so a
  slow build for one key doesn't block unrelated GetOrBuild/Invalidate
  callers.
- SettingsFormHelpers: remove ScheduleDebouncedReload, dead code with
  no call sites whose shared process-static CancellationTokenSource
  would incorrectly cancel unrelated callers' pending reloads if ever
  wired up for more than one debounce source.

Left PendingShortcutEditForm's per-render allocation as-is: it
snapshots the pending draft's name at construction, so caching the
instance across GetContent() calls could show a stale description if
the pending draft changes while the settings page stays open. Not
worth the correctness risk to save one small allocation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

- GitRepoDiscovery: stream child-directory enumeration instead of
  ToArray()-ing every entry up front, so ShouldStop()/MaxDirectoriesScanned
  can short-circuit a very wide directory instead of paying to
  materialize it all first.
- .codex/config.toml: stop committing sandbox_mode=danger-full-access
  and approval_policy=never; these grant full filesystem/network
  access with no approval gate to anyone who clones the repo or runs
  Codex in CI. Set locally (untracked) if wanted.
- QuickShellSettingsManager: clarify that the recent-workspaces setting
  is an on/off toggle (fixed cap, any non-zero value treated as "on"),
  not a literal count, since the raw text field previously implied
  otherwise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 33 changed files in this pull request and generated 3 comments.

Comment thread QuickShell/Services/SettingsFormHelpers.cs Outdated
Comment thread .codex/config.toml
Comment thread QuickShell.Core/Services/GitRepoIndex.cs
- GitRepoIndex.EnsureFresh: clear _refreshInFlight in a catch/finally
  when the discovery task faults. Previously a faulted task stayed
  cached forever, so every future call reused (and re-threw from) the
  same dead task instead of retrying.
- SettingsFormHelpers.ScheduleRefresh: narrow the catch to
  ObjectDisposedException/COMException (the expected "page/COM host
  torn down" cases) instead of swallowing every exception, so real
  bugs still surface.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@tonythethompson tonythethompson merged commit 9476a27 into master Jul 3, 2026
4 checks passed
@tonythethompson tonythethompson deleted the codex/performance-audit branch July 3, 2026 18:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants